//-------------------------------------------------------------------------------------------------------
//      Copyright (c) 2005-2009, Be Nanashi-San 
// 
//      Permission is hereby granted, free of charge, to any person obtaining a 
//      copy of this software and associated documentation files (the "Software"), 
//      to deal in the Software without restriction, including without limitation 
//      the rights to use, copy, modify, merge, publish, distribute, sublicense, 
//      and/or sell copies of the Software, and to permit persons to whom the 
//      Software is furnished to do so, subject to the following conditions: 
// 
//      The above copyright notice and this permission notice shall be included in 
//      all copies or substantial portions of the Software. 
// 
//      THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 
//      IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 
//      FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 
//      AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 
//      LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 
//      FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 
//      DEALINGS IN THE SOFTWARE. 
// 
//      File Name:   	Base2chTextView.cpp
//      Author:      	Be Nanashi-San@598 in "BeOS - Zeta R4" Thread 
//						(http://pc8.2ch.net/test/read.cgi/os/1120676818/598)
//		Description:	互換Base2chTextView.
//						VIEW_LIMITERで表示するレス数を制限できる(待ち時間が我慢ならないようなら)
//						PostMessage()使いまくり。Zetaでは-D_ZETA_USING_DEPRECATED_API_が必要
//						DisplayThreadViewからとMStickerかも使われ、条件が多少違うので煩わしくなっている。
//-------------------------------------------------------------------------------------------------------
#include <Debug.h>
#include <ctype.h>

#include "messages.h"
#include "Base2chTextView.h"
#include "setting.h"
#include "TSettings.h"

MStyle Base2chTextView::PLAIN;
MStyle Base2chTextView::BOLD;
MStyle Base2chTextView::LINK;
MStyle Base2chTextView::SAGE;
MStyle Base2chTextView::AGE;

////////////////////////////////////////////////////////////////////////////////
//  itoa
//
//
////////////////////////////////////////////////////////////////////////////////
static const char*
itoa(int32 n)
{
	static char digits[10];
	digits[sizeof(digits) - 1] = 0;
	int32 d = sizeof(digits) - 2;
	while (0 < n) {
		digits[--d] = (n % 10) + '0';
		n /= 10;
	}
	return digits + d;
}
////////////////////////////////////////////////////////////////////////////////
// quote_strings
// a href = "...." の中身をコピー
// 20051229:コピー削除。アドレスとサイズを返す。
////////////////////////////////////////////////////////////////////////////////
static const char*
quote_strings(const char *data, uint32 length, int32 &size)
{
	const char *text = NULL;
	uint32 i;
	
	for (i = 0; i < length - 1; i++){
	 	if (data[i] == '"') {i++; text = data + i; break;}
	}
	size = 0;
	for (; i < length; i++, size++){
		if (data[i] == '"') break;
	}

	return text;
}
////////////////////////////////////////////////////////////////////////////////
// Unicode2UTF8
// from bemail
// 2005.12.14:Monalist.cppより移動。
////////////////////////////////////////////////////////////////////////////////
static int32
Unicode2UTF8(int32 c, char **out)
{
	char *s = *out;
	int32 ret = 0;

	if (c < 0x80){
		*(s++) = c;
		ret = 1;
	}else if (c < 0x800)
	{
		*(s++) = 0xc0 | (c >> 6);
		*(s++) = 0x80 | (c & 0x3f);
		ret = 2;
	}
	else if (c < 0x10000)
	{
		*(s++) = 0xe0 | (c >> 12);
		*(s++) = 0x80 | ((c >> 6) & 0x3f);
		*(s++) = 0x80 | (c & 0x3f);
		ret = 3;
	}
	else if (c < 0x200000)
	{
		*(s++) = 0xf0 | (c >> 18);
		*(s++) = 0x80 | ((c >> 12) & 0x3f);
		*(s++) = 0x80 | ((c >> 6) & 0x3f);
		*(s++) = 0x80 | (c & 0x3f);
		ret = 4;
	}
	*out = s;
	return ret;
}
////////////////////////////////////////////////////////////////////////////////
// htmlspecialchar2utf8
// html特殊文字の変換。
// 消費した長さを返し、utf8charには変換した文字を返す。
// 2005.12.14:Monalist.cppより移動。
////////////////////////////////////////////////////////////////////////////////
static int32
htmlspecialchar2utf8(const char *s, char *utf8char, int32 &outlen)
{
	if (s[0] == '<') {
		if (s[1] == 'b' && s[2] == '>' ){
			*utf8char = 0;
			outlen = 0;
			return 3;
		}
		
		if (s[1] == '/' && s[2] == 'b' && s[3] == '>' ){
			*utf8char = 0;
			outlen = 0;
			return 4;
		}
	
		int32 i = 1;
		for (const char *br = "br>"; br[0]; br++) {
			while (s[i] == ' ') i++;
			if (br[0] != (s[i]|0x20)){
				outlen = 0;
				return 0;
			}
			i++;
		}
		
	 	*utf8char = '\n';
		outlen = 1;
		return i;
		
	} else if (*s == '&') {

		if(*(s + 1) == '#'){
			char buf[256];
			int32 i = 2; char *p = buf;
			for(; isdigit(s[i]) ; i++) *p++ = s[i];
			*p = 0;
			int32 c = atol(buf);
			outlen = Unicode2UTF8(c, &utf8char);
			
			return i + 1;
		}

		struct convert_info{
			int32 used_length;
			int utf8char;
			const char *htmlchar;
		};
		
		const convert_info htmlchars[] = {
			{4, '>', "&gt;"},
			{4, '<', "&lt;"},
			{6, ' ', "&nbsp;"},
			{6, '\"', "&quot;"},
			{5, '&', "&amp;" },
			{8, '…', "&hellip;" }
			};

		for (uint32 i = 0; i < sizeof(htmlchars)/sizeof(convert_info); i++) {
			int length = htmlchars[i].used_length;
			if (strncmp(s, htmlchars[i].htmlchar, length) == 0){
				*utf8char =  htmlchars[i].utf8char;
				outlen = 1;
				return length;
			}
		}
	}
	*utf8char = 0;
	return 0;
}

////////////////////////////////////////////////////////////////////////////////
// Base2chTextView
//
//
////////////////////////////////////////////////////////////////////////////////
Base2chTextView::Base2chTextView(const char *name, uint32 flags)
	: BTextView(name, flags/*|B_WILL_DRAW|B_FRAME_EVENTS|B_FULL_UPDATE_ON_RESIZE*/)

{
	BTextView::SetStylable(true);
	//20060903
	BTextView::SetDoesUndo(false);
	this->AdjustTextRect();

	SetDisplayFont();
}
////////////////////////////////////////////////////////////////////////////////
// `Base2chTextView
//
//
////////////////////////////////////////////////////////////////////////////////
Base2chTextView::~Base2chTextView()
{

}
////////////////////////////////////////////////////////////////////////////////
// AttachedToWindow
//
//
////////////////////////////////////////////////////////////////////////////////
void
Base2chTextView::AttachedToWindow()
{
	BTextView::MakeEditable(false);
	BTextView::MakeSelectable(true);
}
////////////////////////////////////////////////////////////////////////////////
// Insert
//
//
////////////////////////////////////////////////////////////////////////////////
//Casheに変更。


////////////////////////////////////////////////////////////////////////////////
// is_number
//
// 20060408：BTextView::ByteAt()の呼び出しに修正。
////////////////////////////////////////////////////////////////////////////////
int32
Base2chTextView::is_number(int32 offset)
{
	int32 length = offset;
	while (isdigit(BTextView::ByteAt(length))) length++;
	if (length > offset) return length - offset;

//	20051230:全角数字
	length = offset;
	while (	BTextView::ByteAt(length) == 0xef
	 	&&	BTextView::ByteAt(length + 1) == 0xbc 
	 	&& 	(BTextView::ByteAt(length + 2) >= 0x90 || BTextView::ByteAt(length + 2) <= 0x99) ){
	 		length += 3;
	}

	return length - offset;

}
////////////////////////////////////////////////////////////////////////////////
// FrameResized
// 20090117: haiku向け修正
//
////////////////////////////////////////////////////////////////////////////////
void
Base2chTextView::FrameResized(float width, float height)
{
	BTextView::FrameResized(width, height);
	this->AdjustTextRect();
}
////////////////////////////////////////////////////////////////////////////////
// HtmlParser
//
//
////////////////////////////////////////////////////////////////////////////////
void
Base2chTextView::HtmlParser(const char *text)
{
	const char *action = NULL;
	int32 actionsize = 0;
	int32 offset = 0;
	int32 i;
	for (i = 0; text[i]; i++) {
		//2005.12.14:779氏@BeOS - Zeta R4より。
		char outchar[5]; 
		int32 outlen; 
		int32 changechars = htmlspecialchar2utf8(text + i, outchar, outlen); /* 実際の長さ以上に参照している */ 
		if (changechars) { 
			if (i - offset) Insert(text + offset, i - offset, PLAIN.TextRunArray()); 
			Insert(outchar, outlen, PLAIN.TextRunArray()); 
			i += changechars; 
			offset = i--; 
		}else if (text[i] == '<') {
			if (text[i + 1] == 'a') { 				// start tag
				if (i - offset) BTextView::Insert(text + offset, i - offset, PLAIN.TextRunArray());
				for (int32 j = i + 2; text[j]; j++) {
					if (text[j] == '>') {
						action = quote_strings(text + i + 2, j - 1 - (i + 2), actionsize);
						i = j;
						offset = i + 1;
						break;
					}
				}
			}else if (text[i + 1] == '/') {		// end tag
				this->SetInvoke(action, actionsize, i - offset);
				BTextView::Insert(text + offset, i - offset, LINK.TextRunArray());
				action = NULL;										// タグを閉じるしるしとして
				for (int32 j = i + 2; text[j]; j++) {
					if (text[j] == '>') {
						i = j;
						offset = i + 1;
						break;
					}
				}
			}
		}else if (action == NULL && text[i] == '>' ) {	// >numberはアンカーされないので
			while (isspace(text[i + 1])) i++;		// space cut
			int32 length = is_number(i + 1);
			if (length) {
				BTextView::Insert(text + offset, i + 1 - offset, PLAIN.TextRunArray());
				//20051107
				char url[256];// = "../99999";
				strcpy(url, "../");
				memcpy(url + 3, text + i + 1, length);
				url[3 + length] = '\0';
				this->SetInvoke(url, length);
				BTextView::Insert(text + i + 1, length, LINK.TextRunArray());
				offset = i + 1 + length;
			}
		}else if (text[i] == 'h' && strncmp(text + i, "http://", 7) == 0) {
			if (i - offset) BTextView::Insert(text + offset, i - offset, PLAIN.TextRunArray());
			offset = i;
			i += 7;
			while (text[i]) {
				if (isspace(text[i]) || uint8(text[i]) & 0x80) break;
				i++;
			}
			this->SetInvoke(text + offset, i - offset, i - offset);
			BTextView::Insert(text + offset, i - offset, LINK.TextRunArray());
			offset = i;
		}
	}
	if (i - offset) Insert(text + offset, i - offset, PLAIN.TextRunArray());
}
////////////////////////////////////////////////////////////////////////////////
// SetInvoke
//	現在のテキスト位置(TextLength())からlengthまでをaction範囲とする。
//
////////////////////////////////////////////////////////////////////////////////
void 
Base2chTextView::SetInvoke(const char *name, int32 length)
{
}
////////////////////////////////////////////////////////////////////////////////
// SetInvoke
//	現在のテキスト位置(TextLength())からlengthまでをaction範囲とし
//	action名としてnameからsize分が使われる
////////////////////////////////////////////////////////////////////////////////
void
Base2chTextView::SetInvoke(const char *name, int32 size, int32 length)
{
}
////////////////////////////////////////////////////////////////////////////////
// DiaplayThread
//
// 
///20060718
/////////////////////////////////////////////////////////////////////////////
void
Base2chTextView::DiaplayThread(int32 no, const char **argv)
{
	//スレ番
	BTextView::Insert(itoa(no), BOLD.TextRunArray());
	BTextView::Insert(" : ", PLAIN.TextRunArray());
	//メール欄
	if (*argv[1]) this->SetInvoke(argv[1], strlen(argv[0]));
	//なまえ
	BTextView::Insert(argv[0], (*argv[1] ? SAGE.TextRunArray() : AGE.TextRunArray()));
	BTextView::Insert(" : ", PLAIN.TextRunArray());
	//日付
	BTextView::Insert(argv[2]);
	BTextView::Insert("\n");
	//本文
	this->HtmlParser(argv[3]);
	BTextView::Insert("\n\n");
}
////////////////////////////////////////////////////////////////////////////////
// SetDisplayFont
//
//
////////////////////////////////////////////////////////////////////////////////
void
Base2chTextView::SetDisplayFont()
{
BFont DispFont, DispBoldFont;
	DispFont.SetFamilyAndFace(theSettings().GetString(DISPLAY_RES_FONT_NAME), B_REGULAR_FACE);
	DispFont.SetSize(atof(theSettings().GetString(DISPLAY_RES_FONT_SIZE)));
	DispBoldFont.SetFamilyAndFace(theSettings().GetString(DISPLAY_RES_FONT_NAME), B_BOLD_FACE);
	DispBoldFont.SetSize(atof(theSettings().GetString(DISPLAY_RES_FONT_SIZE)));

	LINK.SetFont(DispFont);
	LINK.SetColor(0, 0, 255);
	PLAIN.SetFont(DispFont);
	PLAIN.SetColor(0, 0, 0);
	BOLD.SetFont(DispBoldFont);
	BOLD.SetColor(0, 0, 0);
	SAGE.SetFont(DispFont);
	SAGE.SetColor(0, 0, 255);
	AGE.SetFont(DispFont);
	AGE.SetColor(0, 128, 0);
}
////////////////////////////////////////////////////////////////////////////////
// AdjustTextRect
//
//
////////////////////////////////////////////////////////////////////////////////
void
Base2chTextView::AdjustTextRect()
{
	BRect aRect;
	aRect = this->Bounds();
	aRect.OffsetTo(B_ORIGIN);
	aRect.InsetBy(3, 3);
	this->SetTextRect(aRect);
}
